package com.zhima.jobhandler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.util.concurrent.AtomicDouble;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.zhima.dto.*;
import com.zhima.entity.TestCaseResult;
import com.zhima.entity.TestCaseTemplate;
import com.zhima.enums.ScoreStrategy;
import com.zhima.feignclient.JudgeRecordFeignClient;
import com.zhima.feignclient.QuestionFeignClient;
import com.zhima.util.RequestUrlBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static com.zhima.constant.Judge0ApiConstant.SUBMISSION_BATCH_URI;
import static com.zhima.constant.Judge0RedisConstant.JUDGE0_TOKEN_KEY;
import static com.zhima.enums.Judge0RequestParamEnums.*;
import static com.zhima.enums.ScoreStrategy.SUM;
import static com.zhima.enums.TestCaseState.*;

@Configuration
public class BatchNotifyJudgeResultJobHandler {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private QuestionFeignClient questionFeignClient;
    @Autowired
    private JudgeRecordFeignClient judgeRecordFeignClient;

    @XxlJob("batchNotifyJudgeResultJobHandler")
    public void batchNotifyJudgeResultJobHandler() {
        // 获取下一次需要批量获取评测结果的token集合
        RedisBatchToken redisBatchToken = JSONUtil.toBean(stringRedisTemplate.opsForList().index(JUDGE0_TOKEN_KEY, 0), RedisBatchToken.class);
        // 不需要处理
        if (redisBatchToken == null || redisBatchToken.getBatchTokens() == null || CharSequenceUtil.isEmpty(redisBatchToken.getBatchTokens())) {
            return;
        }
        // 取出所有token
        String submissionTokens = redisBatchToken.getBatchTokens();
        // 拼接出请求地址
        String batchSubmissionsRequestUrl = RequestUrlBuilder.builder(SUBMISSION_BATCH_URI).addParam(TOKENS, submissionTokens).enable(BASE64_ENCODED).build();
        // 获取结果
        Map batchSubmissionsResultMap = restTemplate.getForObject(batchSubmissionsRequestUrl, Map.class);
        // 转化Judge0的测评结果
        List<Judge0SubmissionResponse> judge0SubmissionResponses = ((List<Map>) batchSubmissionsResultMap.get(SUBMISSIONS.getParamName())).stream().map(a -> BeanUtil.toBean(a, Judge0SubmissionResponse.class)).collect(Collectors.toList());
        // 还在测评中
        if (judge0SubmissionResponses.get(0).getStatus().getId() == Judge0SubmissionStatus.PROCESSING) {
            return;
        }
        // 获取题目的评测计分模板信息
        FeignQuestionJudgeScoreInfoDTO feignQuestionJudgeScoreInfoDTO = questionFeignClient.queryQuestionJudgeScoreInfo(redisBatchToken.getQuestionId());
        // 获取测试模板集合
        List<TestCaseTemplate> testCases = feignQuestionJudgeScoreInfoDTO.getTestCaseTemplates();
        // 总使用内存
        AtomicDouble useMemory = new AtomicDouble();
        // 总使用时间
        AtomicDouble useTime = new AtomicDouble();
        // 是否AC，只要有一个测试点不满足条件就没有AC
        AtomicBoolean accepted = new AtomicBoolean(true);
        // 子任务索引映射测试点配置信息
        Map<Integer, List<TestCaseTemplate>> subTaskIndexMap = testCases.stream().collect(Collectors.groupingBy(TestCaseTemplate::getSubTaskIndex));
        // 子任务索引映射测试点评测信息
        FeignJudgeResult.SubTaskResult[] subTaskResults = new FeignJudgeResult.SubTaskResult[subTaskIndexMap.size()];
        for (int i = 0; i < judge0SubmissionResponses.size(); i++) {
            // 对应测试点的测评结果
            Judge0SubmissionResponse judge0SubmissionResponse = judge0SubmissionResponses.get(i);
            // 测评点模板信息
            TestCaseTemplate testCase = testCases.get(i);
            Integer subTaskIndex = testCase.getSubTaskIndex();
            TestCaseResult testCaseResult = new TestCaseResult();
            // KB为单位
            // judge0返回的是kb，即千比特
            // xkb->((x*1000)/(1024*8))KB
            useMemory.addAndGet((judge0SubmissionResponse.getMemory().doubleValue() * 125) / 1024);
            // ms为单位
            useTime.addAndGet(judge0SubmissionResponse.getTime().doubleValue() * 1000);
            testCaseResult.setTime(judge0SubmissionResponse.getTime());
            testCaseResult.setMemory(judge0SubmissionResponse.getMemory());
            testCaseResult.setIndex(i);
            testCaseResult.setScore(0);
            Judge0SubmissionStatus status = judge0SubmissionResponse.getStatus();
            // 根据judge0的返回结果，蒋结果填充
            switch (status.getId()) {
                case Judge0SubmissionStatus.ACCEPTED:
                    testCaseResult.setScore(testCase.getScore());
                    testCaseResult.setState(ACCEPTED);
                    break;
                case Judge0SubmissionStatus.WRONG_ANSWER:
                    testCaseResult.setState(WRONG_ANSWER);
                    accepted.set(false);
                    break;
                case Judge0SubmissionStatus.TIME_LIMIT_EXCEEDED:
                    testCaseResult.setState(TIME_LIMIT_EXCEEDED);
                    accepted.set(false);
                    break;
                case Judge0SubmissionStatus.RUNTIME_ERROR_SIGFPE:
                case Judge0SubmissionStatus.RUNTIME_ERROR_NZEC:
                case Judge0SubmissionStatus.RUNTIME_ERROR_OTHER:
                case Judge0SubmissionStatus.RUNTIME_ERROR_SIGABRT:
                case Judge0SubmissionStatus.RUNTIME_ERROR_SIGSEGV:
                case Judge0SubmissionStatus.RUNTIME_ERROR_SIGXFSZ:
                    testCaseResult.setState(RUNTIME_ERROR);
                    accepted.set(false);
                    break;
                case Judge0SubmissionStatus.INTERNAL_ERROR:
                case Judge0SubmissionStatus.IN_QUEUE:
                case Judge0SubmissionStatus.PROCESSING:
                case Judge0SubmissionStatus.EXEC_FORMAT_ERROR:
                    testCaseResult.setState(SYSTEM_ERROR);
                    accepted.set(false);
                    break;
                default:
                    // 看看是否超内存限制了
                    if (testCase.getMaxMemoryLimit() < judge0SubmissionResponse.getMemory().intValue()) {
                        testCaseResult.setState(MEMORY_LIMIT_EXCEEDED);
                    } else {
                        testCaseResult.setState(SYSTEM_ERROR);
                    }
                    accepted.set(false);
            }
            if (subTaskResults[subTaskIndex] == null) {
                subTaskResults[subTaskIndex] = new FeignJudgeResult.SubTaskResult();
                subTaskResults[subTaskIndex].setScoreStrategy(testCase.getScoreStrategy());
                subTaskResults[subTaskIndex].setTestCaseResults(new ArrayList<>());
            }
            // 找到对应子任务的测试点
            subTaskResults[subTaskIndex].getTestCaseResults().add(testCaseResult);
        }
        FeignJudgeResult feignJudgeResult = new FeignJudgeResult();
        feignJudgeResult.setSubTaskResults(subTaskResults);
        feignJudgeResult.setScoreStrategy(feignQuestionJudgeScoreInfoDTO.getScoreStrategy());
        feignJudgeResult.setUseTime(BigDecimal.valueOf(useTime.doubleValue()));
        feignJudgeResult.setUseMemory(BigDecimal.valueOf(useMemory.doubleValue()));
        feignJudgeResult.setAccepted(accepted.get());
        calculateScore(subTaskResults, feignQuestionJudgeScoreInfoDTO, feignJudgeResult);
        // 统一收集结果，通知结果
        judgeRecordFeignClient.updateJudge0Result(redisBatchToken.getJudgeRecordId(), feignJudgeResult);
        // 本条token集合处理完毕，从Redis中删除
        stringRedisTemplate.opsForList().leftPop(JUDGE0_TOKEN_KEY);
    }

    /**
     * 按照子任务分组计算总分
     *
     * @param subTaskResults
     * @param feignQuestionJudgeScoreInfoDTO
     * @param feignJudgeResult
     */
    private void calculateScore(FeignJudgeResult.SubTaskResult[] subTaskResults, FeignQuestionJudgeScoreInfoDTO feignQuestionJudgeScoreInfoDTO, FeignJudgeResult feignJudgeResult) {
        int[] subTaskScores = new int[subTaskResults.length];
        for (int subTaskIndex = 0; subTaskIndex < subTaskResults.length; subTaskIndex++) {
            FeignJudgeResult.SubTaskResult subTaskResult = subTaskResults[subTaskIndex];
            ScoreStrategy scoreStrategy = subTaskResult.getScoreStrategy();
            int[] scores = new int[subTaskResult.getTestCaseResults().size()];
            for (int i = 0; i < scores.length; i++) {
                scores[i] = subTaskResult.getTestCaseResults().get(i).getScore();
            }
            switch (scoreStrategy) {
                case MAX:
                case MIN:
                case SUM:
                    subTaskScores[subTaskIndex] = scoreStrategy.calculation(scores);
                    break;
                case SPJ:
                    subTaskScores[subTaskIndex] = SUM.calculation(scores);
                    break;
            }
            subTaskResult.setScoreStrategy(scoreStrategy);
            subTaskResult.setScore(subTaskScores[subTaskIndex]);
        }
        ScoreStrategy totalScoreStrategy = feignQuestionJudgeScoreInfoDTO.getScoreStrategy();
        switch (totalScoreStrategy) {
            case SUM:
            case MIN:
            case MAX:
                feignJudgeResult.setScore(totalScoreStrategy.calculation(subTaskScores));
                break;
            case SPJ:
                feignJudgeResult.setScore(SUM.calculation(subTaskScores));
                break;
        }
    }
}
